package stocc.converter;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;

import stocc.converter.exceptions.CodeConflictException;
import stocc.converter.exceptions.NameConflictException;
import stocc.converter.exceptions.VisibilityConflictException;
import stocc.logger.Level;
import stocc.subject.Member;
import stocc.subject.Method;
import stocc.subject.Subject;
import stocc.subject.View;
import stocc.subject.settings.ConversionSettings;
import stocc.subject.settings.OnNameConflict;

public class ObjectBuilder {

	List<Subject> subjects;
	Hashtable<String, View> namedViews;
	
	public ObjectBuilder(){
		namedViews = new Hashtable<String, View>();
	}
	
	public List<View> build(List<Subject> subjects)
		throws 	NameConflictException,
				CodeConflictException,
				VisibilityConflictException
	{
		for(Subject s : subjects){
			addViewsToList(s, s.getViews());
		}
		
		List<View> retlist = new ArrayList<View>();
		Enumeration<View> views = namedViews.elements(); 
		while(views.hasMoreElements()){
			retlist.add(views.nextElement());
		}
		return retlist;
		
	}
	
	private void addViewsToList(Subject subject, List<View> from) 		
	throws 	NameConflictException,
			CodeConflictException,
			VisibilityConflictException
	{
		for(View v : from){
			//Load settings from subject if no specific settings
			//are set for this view.
			if(v.getSettings() == null){
				v.setSettings(subject.getSettings());
			}
			
			//Check if another view already has the same name
			if(namedViews.containsKey(v.getName())){
				//DEBUG
				log("Name collission: " + v.getName(), Level.DEBUG);
				
				View oldV = namedViews.remove(v.getName());
				View nv = mergeViews(oldV, v, subject);
				namedViews.put(nv.getName(), nv);
				log("Merged view " + v.getName() + " with previous view in list", Level.INFO);
			}else{
				namedViews.put(v.getName(), v);
				log("Added view " + v.getName() + " to list", Level.INFO);
				//DEBUG
				log("Name free: " + v.getName(), Level.DEBUG);
			}
			
		}
	}
	
	/**
	 * Merges two views.
	 * @param oldV A view to check for conflicts. This view is potentially overwritten
	 * @param newV A view to check for conflicts. This view is left unchanged.
	 * @param newS The subject containing newV
	 * @return oldV
	 * @throws NameConflictException
	 * @throws CodeConflictException
	 * @throws VisibilityConflictException
	 */
	private View mergeViews(View oldV, View newV, Subject newS) 		
		throws 	NameConflictException,
				CodeConflictException,
				VisibilityConflictException
		{
		ConversionSettings oldSettings = oldV.getSettings();
		ConversionSettings newSettings = newV.getSettings();
		String subjectName = newS.getName();
		
		//If both views have their OnNameConflict set to OVERRIDE
		//Throw exception
		if(	oldSettings.onNameConflict == OnNameConflict.OVERRIDE &&
			newSettings.onNameConflict == OnNameConflict.OVERRIDE){
			throw new NameConflictException(
					"" + subjectName +
					"::" + newV.getName() + " and " + 
					oldV.getName() + 
					" both have their OnNameConflict set to OVERRIDE");
		}
		
		//If at least one of the views have their OnNameConflict set to DIE
		//Throw exception
		if(	oldSettings.onNameConflict == OnNameConflict.DIE ||
			newSettings.onNameConflict == OnNameConflict.DIE){
			throw new NameConflictException(
					"" + subjectName +
					"::" + newV.getName() + " or some previous " + 
					oldV.getName() + 
					" has its OnNameConflict set to DIE");
		}
		
		//Placeholder for objects to be removed after the iteration
		List<Object> toRemove = new ArrayList<Object>();
		List<Object> toAdd = new ArrayList<Object>();
		
		//Check for conflicts among members
		for(Member nm : newV.getMembers()){
			boolean allowAdd = true;
			for(Member om : oldV.getMembers()){
				if(toAdd.contains(nm) || toRemove.contains(om)){
					//We've already handled this method
					break; //Assume no further conflicts for this om
				}				
				log("MemberLoop checking old: " + om.getName() + " new: " + nm.getName(), Level.DEBUG);
				
				if(toAdd.contains(nm)){
					//DEBUG
					if(toAdd.contains(nm)){
						log("toAdd contains " + nm.getName(), Level.DEBUG);
						allowAdd = false;
					}
					//We've already handled this method
					continue; //Assume no further conflicts for this om
				}
				if(om.getName().equals(nm.getName())){
					//DEBUG
					log("Name equal: " + om.getName() + " and " + nm.getName(), Level.DEBUG);
					
					log("old: " + oldSettings.onNameConflict.toString() + 
						"new: " + newSettings.onNameConflict.toString(), Level.DEBUG);
					

					//Check if new view is set to override
					if(	oldSettings.onNameConflict == OnNameConflict.MERGE &&
						newSettings.onNameConflict == OnNameConflict.OVERRIDE){
						//LOG
						log("Replaced " + om.getName() + " old:MERGE, new:OVERRIDE", Level.INFO);
						
						toRemove.add(om);
						toAdd.add(nm);
						allowAdd = false;
						break;
					}
					
					//Check if old view was set to override.
					if(oldSettings.onNameConflict == OnNameConflict.OVERRIDE){
						log(oldV.getName() + " was set to OVERRIDE, not able to add new member " + nm.getName(), Level.INFO);
						allowAdd = false;
						break;
					}		
					
					//Check for visibility conflict
					if(om.getVisibility() != nm.getVisibility()){
						throw new VisibilityConflictException(
								newS.getName() + "::" + 
								newV.getName() + "::" +
								nm.getName() +
								" has its visibility set to " + 
								nm.getVisibility().toString() + 
								" whereas the previous visibility was " + 
								om.getVisibility().toString());
					}
					

					if(	oldSettings.onNameConflict == OnNameConflict.MERGE && 
						newSettings.onNameConflict == OnNameConflict.MERGE){
						log("Both settings were set to merge, drop and continue", Level.DEBUG);
						allowAdd = false;
						break;
					}
					
					//TODO Check for default value conflict
					
					//If we get here, no conflict was detected,
					//so we keep the old member
					allowAdd = false;
				}else{
					//DEBUG
					log("Name NOT equal: " + om.getName() + " and " + nm.getName() + ". Keep checking", Level.DEBUG);
				}
			}
			if(allowAdd){
				//DEBUG
				log("No conflict found. Adding: " + nm.getName(), Level.DEBUG);
				
				//No conflict, add new member.
				toAdd.add(nm);
			}			
		}
		
		//Remove objects in placeholder
		for(Object o : toRemove){
			oldV.getMembers().remove(o);
			
			//DEBUG
			Member temp = (Member) o;
			log("Physically removing member: " + temp.getName(), Level.DEBUG);
		}
		toRemove.clear();
		
		//Add objects from placeholder
		for(Object m : toAdd){
			oldV.addMember((Member) m);
			
			//DEBUG
			Member temp = (Member) m;
			log("Physically adding member: " + temp.getName(), Level.DEBUG);			
		}
		toAdd.clear();
		

		//Check for conflicts among methods
		
		//TODO If methods have the same name, but different params or retType, just add.
		
		for(Method nm : newV.getMethods()){
			boolean allowAdd = true;
			for(Method om : oldV.getMethods()){
				if(toAdd.contains(nm) || toRemove.contains(om)){
					//We've already handled this method
					allowAdd = false;
					continue; //Assume no further conflicts for this om
				}

				log("MethodLoop checking old: " + om.getName() + " new: " + nm.getName(), Level.DEBUG);
				
				if(toAdd.contains(nm)){
					//DEBUG
					log("toAdd contains " + nm.getName(), Level.DEBUG);
					allowAdd = false;
					//We've already handled this method
					continue; //Assume no further conflicts for this om
				}				
				
				if(om.getName().equals(nm.getName())){
					//DEBUG
					log("Name equal: " + om.getName() + " and " + nm.getName(), Level.DEBUG);

					//Check for override
					if(	oldSettings.onNameConflict == OnNameConflict.MERGE &&
						newSettings.onNameConflict == OnNameConflict.OVERRIDE){
						//LOG
						log("Replaced " + om.getName() + " old:MERGE, new:OVERRIDE", Level.INFO);
						toRemove.add(om);
						toAdd.add(nm);
						allowAdd = false;
					}
					
					//Check for visibility conflict
					if(om.getVisibility() != nm.getVisibility()){
						throw new VisibilityConflictException(
								newS.getName() + "::" + 
								newV.getName() + "::" +
								nm.getName() +
								" has its visibility set to " + 
								nm.getVisibility().toString() + 
								" whereas the previous visibility was " + 
								om.getVisibility().toString());
					}
					
					//Check if old view was set to override.
					if(oldSettings.onNameConflict == OnNameConflict.OVERRIDE){
						log(oldV.getName() + " was set to OVERRIDE, not able to add new method " + nm.getName(), Level.INFO);
						allowAdd = false;
						break;
					}
					
					//Check for code conflict
					if(!om.getCode().equals(nm.getCode())){
						throw new CodeConflictException(
								newS.getName() + "::" + 
								newV.getName() + "::" +
								nm.getName() +
								" has different code compared to " + 
								om.getName() + 
								" but conversion was set to merge ");
					}					

					if(	oldSettings.onNameConflict == OnNameConflict.MERGE && 
							newSettings.onNameConflict == OnNameConflict.MERGE){
							log("Both settings were set to merge, drop and continue", Level.DEBUG);
							allowAdd = false;
							break;
					}					
					
					//If we get here, no conflict was detected,
					//so we keep the old member
				}else{
					//DEBUG
					log("Name NOT equal: " + om.getName() + " and " + nm.getName(), Level.DEBUG);
				}
			}
			if(allowAdd){
				//DEBUG
				log("No conflict found. Adding: " + nm.getName(), Level.DEBUG);
				
				//No conflict, add new member.
				toAdd.add(nm);
			}
		}

		//Remove objects in placeholder
		for(Object o : toRemove){
			oldV.getMethods().remove(o);
		}
		toRemove.clear();		

		//Add objects from placeholder
		for(Object m : toAdd){
			oldV.addMethod((Method) m);
		}
		toAdd.clear();
		
		
		//Just merge imports for now
		oldV.getImports().addAll(newV.getImports());
		
		//If old view was set to MERGE
		//and new view was set to OVERRIDE:
		if(	oldSettings.onNameConflict == OnNameConflict.MERGE &&
			newSettings.onNameConflict == OnNameConflict.OVERRIDE){
			oldV.getSettings().onNameConflict = OnNameConflict.OVERRIDE;
		}

		return oldV;
	}
	
	/**
	 * Logging method, add this to any class that does logging
	 * @param message The message to be logged
	 * @param level The logging level of the message
	 */
	private static void log(String message, stocc.logger.Level level){
		stocc.logger.Logger.getLogger().log(level, "Builder", message);
	}	
}
